%{
#include "hoc.h"		/* for Symbol_t and Inst_t */
#include "symbol.h"		/* for symbol table handler */
#include "code.h"		/* for the virtual machine */
#include "dump.h"		/* for dump and trace */

/* Prototype Functions */
int yylex();
int yyerror(const char *);
void defnonly(const char *);

/* External Functions */
extern void initsym();	/* provided by init.c */

/* Macros to lay down code */
#define code2(c1,c2)	code(c1); code(c2)
#define code3(c1,c2,c3)	code(c1); code(c2); code(c3)

/* Global Variables */
int	indef = 0;			/* Inside Proc/Func defintion */
%}
%union {			/* stack type */
	Symbol_t*	ptr_sym;	/* Symbol Table Pointer */
	Inst_t*		inst;		/* Machine Instruction */
	int			iarg;		/* Number of arguments */
}
%token	<ptr_sym>	DUMP TRACE ALL
%token	<ptr_sym>	NUMBER STRING PRINT VAR BLTIN UNDEF WHILE IF ELSE CHARACTER BOOLEAN
%token	<ptr_sym>	FUNCTION PROCEDURE RETURN FUNC PROC READ
%token	<ptr_sym>	INT BOOL FLOAT CHAR TRUE FALSE
%token	<iarg>		ARG
%type	<inst>		expr stmt asgn prlist stmtlist
%type	<inst>		cond while if begin end
%type	<ptr_sym>	procname
%type	<iarg>		arglist
%right	'='
%left	OR
%left	AND
%left	GT GE LT LE EQ NE
%left	'+' '-'		/* left associate, same precedence */
%left	'*' '/' '%'	/* left assoc, higher precedence */
%left	UNARY NOT	/* Unary Operators */
%right	'^'			/* Exponentiation */
%start list
%%
list: /* nothing */
	| list '\n'
	| list defn '\n'
	| list asgn '\n'		{ code2(drop, STOP); return 1; }
	| list stmt '\n'		{ code(STOP); return 1; }
	| list expr '\n'		{ code2(print, STOP); return 1; }
	| list error '\n'		{ yyerrok; }
	;

asgn: INT VAR '=' expr			{ $$=$4; code3(varpush, (Inst_t)$2, assign); }
	| FLOAT VAR '=' expr		{ $$=$4; code3(varpush, (Inst_t)$2, assign); }
	| CHAR VAR '=' expr		{ $$=$4; code3(varpush, (Inst_t)$2, assign); }
	| BOOL VAR '=' expr		{ $$=$4; code3(varpush, (Inst_t)$2, assign); }
	| VAR '=' expr			{ $$=$3; code3(varpush, (Inst_t)$1, assign); }
	| ARG '=' expr			{ $$=$3; defnonly("$");
							  code2(argassign, (Inst_t)$1); }
	;

stmt: expr						{ code(drop); }
	| DUMP						{ dump_last(); }
	| DUMP procname				{ dump_proc($2); }
	| DUMP ALL					{ dump_all(); }
	| trace stmt
	| RETURN					{ defnonly("return"); code(procret); }
	| RETURN expr				{ $$ = $2; defnonly("return");
								  code(funcret); }
	| PROCEDURE begin '(' arglist ')'
						{ $$ = $2; code3(call, (Inst_t)$1, (Inst_t)$4); }
	| PRINT prlist				{ $$ = $2; }
	| while cond stmt end		{ /* while loop */
		($1)[1] = (Inst_t)$3;	/* Body of Loop */
		($1)[2] = (Inst_t)$4;	/* end, if cond fails */
	}
	| if cond stmt end			{ /* If-then */
		($1)[1] = (Inst_t)$3;	/* Then part */
		($1)[3] = (Inst_t)$4;	/* end, if cond fails */
	}
	| if cond stmt end ELSE stmt end { /* If-then-else */
		($1)[1] = (Inst_t)$3;	/* Then Part */
		($1)[2] = (Inst_t)$6;	/* Else Part */
		($1)[3] = (Inst_t)$7;	/* end, if cond fails */
	}
	| '{' stmtlist '}'			{ $$ = $2; }
	;

cond: '(' expr ')'				{ code(STOP); $$ = $2; }
	;

while: WHILE					{ $$ = code3(whilecode, STOP, STOP); }
	;

if: IF				{ $$ = code(ifcode); code3(STOP, STOP, STOP); }
	;

begin: /* nothing */			{ $$ = ptr_prog; }
	;

end: /* nothing */				{ code(STOP); $$ = ptr_prog; }
	;

stmtlist: /* nothing */			{ $$ = ptr_prog; }
	| stmtlist '\n'
	| stmtlist stmt
	;

expr: NUMBER				{ $$ = code2(constpush, (Inst_t)$1); }
	| CHARACTER				{ $$ = code2(constpush, (Inst_t)$1); }
	| BOOLEAN				{ $$ = code2(constpush, (Inst_t)$1); }
	| VAR 					{ $$ = code3(varpush, (Inst_t)$1, eval); }
	| ARG					{ defnonly("$"); $$ = code2(arg, (Inst_t)$1); }
	| asgn
	| FUNCTION begin '(' arglist ')'
						{ $$ = $2; code3(call, (Inst_t)$1, (Inst_t)$4); }
	| READ '(' VAR ')'		{ $$ = code2(varread, (Inst_t)$3); }
	| BLTIN '(' expr ')'	{ $$ = $3;
							  code2(bltin, (Inst_t)$1->u.ptr_dfunc); }
	| expr '+' expr			{ code(hoc_add); }
	| expr '-' expr			{ code(hoc_sub); }
	| expr '*' expr 		{ code(hoc_mul); }
	| expr '/' expr			{ code(hoc_div); }
	| expr '%' expr			{ code(hoc_mod); }
	| expr '^' expr			{ code(hoc_power); }
	| '-' expr %prec UNARY	{ $$ = $2; code(hoc_negate); }
	| '+' expr %prec UNARY	{ $$ = $2; }
	| '(' expr ')'			{ $$ = $2; }
	| expr GT expr			{ code(hoc_gt); }
	| expr GE expr			{ code(hoc_ge); }
	| expr LT expr			{ code(hoc_lt); }
	| expr LE expr			{ code(hoc_le); }
	| expr EQ expr			{ code(hoc_eq); }
	| expr NE expr			{ code(hoc_ne); }
	| expr AND expr			{ code(hoc_and); }
	| expr OR expr			{ code(hoc_or); }
	| NOT expr				{ $$ = $2; code(hoc_not); }
	;

prlist: expr				{ code(prexpr); }
	| STRING				{ $$ = code2(prstr, (Inst_t)$1); }
	| prlist ',' expr		{ code(prexpr); }
	| prlist ',' STRING		{ code2(prstr, (Inst_t)$3); }
	;

defn: FUNC procname			{ $2->itype=FUNCTION, indef=1; }
			'(' ')' stmt	{ code(funcret); define($2); indef=0; }
	| PROC procname			{ $2->itype=PROCEDURE, indef=1; }
			'(' ')' stmt	{ code(procret); define($2); indef=0; }
	;

procname: VAR
	| FUNCTION
	| PROCEDURE
	;

arglist: /* nothing */		{ $$ = 0; }
	| expr					{ $$ = 1; }
	| arglist ',' expr		{ $$ = $1 + 1; }
	;

trace: TRACE				{ code(trace); }
	 ;
%%
#include <stdio.h>		/* for getc, ungetc, fscanf, printf */
#include <ctype.h>		/* for isdigit, isalpha and isalnum */
#include <string.h>		/* for strlen and strchr */
#include <signal.h>		/* for signal handling */
#include <setjmp.h>		/* for longjump */

/* Prototype Functions */
void warning(const char*, const char*);

/* Signal Handlers */
#ifdef __cplusplus
extern "C" {
	void fpecatch(int);
	void intcatch(int);
}
#else
	void fpecatch(int);
	void intcatch(int);
#endif

/* Global Variables */
jmp_buf begin;
char*	progname;		/* Program Name */
int		lineno = 1;		/* Current line number */
int		cchar;			/* Current parse character */
Symbol_t *pLastSym = NULL; /* Last symbol */

char	**flist;		/* List of input files */
char*	infile;			/* Current intput file */
FILE*	fin;			/* Input File Handle */

/* Config Variables / Magic Numbers */
#define MAX_SYMBOL	100		/* Size of a Symbol */
#define MAX_STRING	1024	/* Size of a String */

/********** Parser *********/

/* Look ahead one char */
int follow(int cexpect, int iyes, int ino) {
	cchar = getc(fin);
	if ( cchar == cexpect ) {
		return iyes;
	}

	ungetc(cchar, fin);
	return ino;
}

/* Get next char with \'s interpreted */
char backslash(int cchar) {
	static char transtab[] = "b\bf\fn\nr\rt\t";
	if ( cchar != '\\' ) {
		return cchar;
	}
	cchar = getc(fin);
	if ( islower(cchar) && strchr(transtab, cchar) ) {
		return strchr(transtab, cchar)[1];
	}
	return cchar;
}

int yylex () { /* hoc6 */
	/* Eat the white space */
	do {
		cchar = getc(fin);
	} while ( cchar == ' ' || cchar == '\t' );

	/* Comment */
	if ( cchar == '#' ) {
		while ( cchar != '\n' && cchar != EOF ) {
			cchar = getc(fin);
		}
	}

	/* End of File */
	if ( cchar == EOF ) {
		return 0;
	}

	/* Number */
	if ( cchar == '.' || isdigit(cchar) ) {
		/* pLastSym stores the datatype of the number we are now reading */
			double dval;
			ungetc(cchar, fin);
			fscanf(fin, "%lf", &dval);
			/* dval stores a double - is it actually an int? */
			if((double)(int)dval==dval)
			{
				/*printf("Is an int %d\n",(int)dval);*/
				yylval.ptr_sym = install("", NUMBER, INT,(int)dval);
			}
			else
			{
				/*printf("must be a double %g\n",dval);*/
			      yylval.ptr_sym = install("", NUMBER, FLOAT,dval);
			}
			/*yylval.ptr_sym = install("", NUMBER, pLastSym->				datatype,dval);*/
		return NUMBER;
	}

	/* Argument */
	if ( cchar == '$' ) {
		int iarg = 0;
		cchar = getc(fin);
		while ( isdigit(cchar) ) {
			iarg = 10 * iarg + (cchar - '0');
			cchar = getc(fin);
		}
		ungetc(cchar, fin);
		if ( iarg == 0 ) {
			execerror("strange $...", NULL);
		}
		yylval.iarg = iarg;
		return ARG;
	}

	/* Quoted String */
	if ( cchar == '"' ) {
		char cbuf[MAX_STRING];
		char *ptr_cbuf = cbuf;

		cchar = getc(fin);
		while ( cchar != '"' ) {
			*ptr_cbuf = '\0';
			if ( cchar == '\n' || cchar == EOF ) {
				execerror("Missing Double Quote", cbuf);
			}
			if ( ptr_cbuf >= (cbuf + MAX_STRING - 1) ) {
				execerror("String too long", cbuf);
			}
			*(ptr_cbuf++) = backslash(cchar);
			cchar = getc(fin);
		}

		*ptr_cbuf = '\0';
		yylval.ptr_sym = (Symbol_t *)emalloc(strlen(cbuf)+1);
		strcpy((char *)yylval.ptr_sym, cbuf);
		return STRING;
	}

	/* Character */
	if ( cchar == '\'' ) {
		char cbuf;
		/*char *ptr_cbuf = cbuf;*/
		
		/*Get the actual character */
		cbuf = getc(fin);
		
		/* Check quote follows character */		
		cchar = getc(fin);
		if(cchar != '\'')		
		{
			execerror("Error: Attempting to assign more than one character to variable of type character", NULL );
		}
		
		/* Install char in symbol table */
		yylval.ptr_sym = install("", CHARACTER, CHAR,(double)cbuf);	
		
		return CHARACTER;
	}

	/* Symbol */
	if ( isalpha(cchar) || cchar == '_' ) {
		Symbol_t *ptr_sym;
		char cbuf[MAX_SYMBOL];
		char *ptr_cbuf = cbuf;

		do {
			if ( ptr_cbuf >= (cbuf + MAX_SYMBOL - 1) ) {
				*ptr_cbuf = '\0';
				execerror("Name too long", cbuf);
			}

			*(ptr_cbuf++) = cchar;
			cchar = getc(fin);
		} while ( isalnum(cchar) || cchar == '_' );

		ungetc(cchar, fin);
		*ptr_cbuf = '\0';

		ptr_sym = lookup(cbuf);
		if ( ptr_sym == NULL ) {
			/* Install new variable name if last symbol was a type*/
				if((pLastSym != NULL) && (pLastSym->itype == INT || pLastSym->itype == FLOAT || pLastSym->itype == CHAR || pLastSym->itype == BOOL || pLastSym->itype == PROC || pLastSym->itype == FUNC)){
				ptr_sym = install(cbuf, UNDEF, pLastSym->itype, 0.0);
			}
			else {
				execerror("No type declared for variable", cbuf);
			}
		}
		else if(ptr_sym->itype == TRUE || ptr_sym->itype == FALSE)
		{			
			yylval.ptr_sym = ptr_sym;
			pLastSym = ptr_sym;			
			return BOOLEAN;
		}

		yylval.ptr_sym = ptr_sym;
		pLastSym = ptr_sym;
		return ptr_sym->itype == UNDEF ? VAR : ptr_sym->itype;
	}

	/* Equvilance operators */
	switch (cchar) {
	case '>':	return follow('=', GE, GT);
	case '<':	return follow('=', LE, LT);
	case '=':	return follow('=', EQ, '=');
	case '!':	return follow('=', NE, NOT);
	case '|':	return follow('|', OR, '|');
	case '&':	return follow('&', AND, '&');
	case ';':	return '\n';
	case '\n':	lineno++; return '\n';
	default:	return cchar;
	}
}

/********** Error Handling **********/

void warning(const char* ptr_cstr, const char* ptr_ctype) {
	if ( infile ) {
		fprintf(stderr, "%s(%d): %s", infile, lineno, ptr_cstr);
	} else {
		fprintf(stderr, "%s:%d: %s", progname, lineno, ptr_cstr);
	}

	if ( ptr_ctype ) {
		fprintf(stderr, " %s", ptr_ctype);
	}
	fprintf(stderr, "\n");

	/* Flush the rest of the line */
	while ( cchar != '\n' && cchar != EOF ) {
		cchar = getc(fin);
	}
	ungetc(cchar, fin);
}

/* Report compile-time errors */
int yyerror(const char* ptr_cstr) {
	warning(ptr_cstr, NULL);
	return 0;
}

/* Recover from Run-Time error */
void execerror(const char* ptr_cstr, const char* ptr_ctype) {
	warning(ptr_cstr, ptr_ctype);
	fseek(fin, 0, SEEK_END);	/* Flush rest of file */
	longjmp(begin, 0);
}

/* Catch Exceptions */

#ifdef __cplusplus
extern "C" {
#endif

/* Catch the Floating Point Exception */
void fpecatch(int value) {
	execerror("Floating point excetion", NULL);
}

/* Catch Ctrl-C Interrupts */
void intcatch(int value) {
	execerror("Interrupted", NULL);
}

#ifdef __cplusplus
}
#endif

/* Warn if illegal definition */
void defnonly(const char *ptr_str) {
	if ( indef == 0 ) {
		execerror(ptr_str, "used outside definition");
	}
}

/********** Input File Handling **********/

/* Process one statement at a time */
void run() {
	int fcompiled;				/* Should be a bool in C++ */

	setjmp(begin);				/* Set Restart Point */

	signal(SIGFPE, fpecatch);	/* Catch Floating Point Exceptions */
	signal(SIGINT, intcatch);	/* Catch Interrupts (Ctrl-C) */

	fcompiled = 1;				/* true */
	while ( fcompiled ) {
		initcode();				/* Initlise the Virtual Machine */
		fcompiled = yyparse();	/* Parse the code (Compile it) */
		if ( fcompiled ) {		/* Did it compile? */
			execute(ptr_base);	/* Yes => Execute the code */
		}
	}
}

/* Select file for input */
int nextfile() {
	/* Are we reading a file */
	if ( fin ) {
		if ( fin != stdin ) {
			/* We have just finished reading from a file so close it */
			fclose(fin);
		}
	}

	/* Have we reached the end of the file list */
	if ( *flist == NULL ) {
		return 0;
	}

	/* Re-Initlise for the next file */
	infile = *flist;	/* Filename */
	lineno = 1;			/* Current Line Number */
	flist++;			/* Move on to next file */

	if ( strcmp( infile, "-" ) == 0 ) {
		/* If the file name is - we are using the standard input */
		fin = stdin;
		infile = NULL;
	} else {
		/* We have a real file name */
		fin = fopen( infile, "r" );
		if ( fin == NULL ) {
			/* But we can't open it */
			fprintf(stderr, "%s: can't open %s\n", progname, infile);
			/* Move on to the next file in the list */
			return nextfile();
		}
	}
	return 1;
}

/* Make a list of filenames from the arg list */
void mkfilelist(int argc, char *argv[]) {
	int i = 0;

	/* Reserve space for argc + 2 pointers */
	flist = (char **)emalloc( sizeof(char *) * ( argc + 2 ) );

	if ( argc == 0 ) {
		/* Use standard input by default */
		flist[i++] = "-";
	} else {
		while ( i < argc ) {
			flist[i++] = argv[i];
		}
	}

	/* Terminate File list */
	flist[i] = NULL;
}

/* Finally the main code */
int main (int argc, char *argv[]) { /* hoc6 */
	progname = argv[0];				/* Remember the program name */

	mkfilelist(argc-1, &argv[1]);	/* Make a list of input files */
	initsym();						/* Initlise the Symbol Table */
	while ( nextfile() ) {			/* While we have input */
		run();						/* Process the input */
	}
	return 0;
}
